﻿using System;
using System.Text;

namespace SoftwareConsulting.BI11.NumericalMethods.MatrixAlgebra
{
    /// <summary>
    /// Класс матрицы
    /// </summary>
    public class Matrix
    {
        private double[,] _components;
        private LowerUpperPermutationDecomposition _lowerUpperPermutationDecomposition;

        protected LowerUpperPermutationDecomposition LowerUpperPermutationDecomposition
        {
            get
            {
                return _lowerUpperPermutationDecomposition;
            }
            set
            {
                _lowerUpperPermutationDecomposition = value;
            }
        }

        /// <summary>
        /// Возвращает компоненты матрицы
        /// </summary>
        internal double[,] Components
        {
            get 
            { 
                return _components; 
            }
        }

        /// <summary>
        /// Устанавливает элементы матрицы
        /// </summary>
        /// <param name="components"></param>
        protected void SetComponents(double[,] components)
        {
            _components = components;
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="elements"></param>
        public Matrix(double[,] elements)
        {
            _components = elements;
        }

        /// <summary>
        /// Конструктор класса
        /// </summary>
        /// <param name="rowsNumber"></param>
        /// <param name="columnsNumber"></param>
        public Matrix(int rowsNumber, int columnsNumber)
        {
            if (rowsNumber <= 0 || columnsNumber <= 0)
                throw new IndexOutOfRangeException();
            _components = new double[rowsNumber, columnsNumber];
            Clear();
        }

        /// <summary>
        /// Складывает с указанной матрицей
        /// </summary>
        /// <param name="secondMatrix"></param>
        public void Add(Matrix secondMatrix)
        {
            if (secondMatrix.RowsCount != this.RowsCount || secondMatrix.ColumnsCount != this.ColumnsCount)
                throw new BIMatrixCantAddOrSubtractException(this.RowsCount, this.ColumnsCount, secondMatrix.RowsCount, secondMatrix.ColumnsCount, null);
            int m = _components.GetLength(1);
            for (int i = 0; i < _components.GetLength(0); i++)
            {
                for (int j = 0; j < m; j++)
                    _components[i, j] += secondMatrix[i, j];
            }
        }        

        /// <summary>
        /// Возвращает результат сложения двух матриц
        /// </summary>
        /// <param name="B"></param>
        /// <param name="A"></param>
        /// <returns></returns>
        public static Matrix operator +(Matrix B, Matrix A)
        {
            if (A.RowsCount != B.RowsCount || A.ColumnsCount != B.ColumnsCount)
                throw new BIMatrixCantAddOrSubtractException(A.RowsCount, A.ColumnsCount, B.RowsCount, B.ColumnsCount, null);
            return new Matrix(B.GetResultOfComponentsAddition(A));
        }

        /// <summary>
        /// Возвращает результат сложения с указанной матрицей
        /// </summary>
        /// <param name="secondMatrix"></param>
        /// <returns></returns>
        protected double[,] GetResultOfComponentsAddition(Matrix secondMatrix)
        {
            int n = this.RowsCount;
            int m = this.ColumnsCount;
            double[,] newComponents = new double[n, m];
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < m; j++)
                    newComponents[i, j] = _components[i, j] + secondMatrix._components[i, j];
            }
            return newComponents;
        }

        /// <summary>
        /// Очищает матрицу
        /// </summary>
        public void Clear()
        {
            Array.Clear(_components, 0, _components.Length);
        }

        /// <summary>
        /// Возвращает число столбцов в матрице
        /// </summary>
        public int ColumnsCount
        {
            get 
            {
                return _components.GetLength(1); 
            }
        }

        /// <summary>
        /// Возвращает указанный элемент матрицы
        /// </summary>
        /// <param name="n"></param>
        /// <param name="m"></param>
        /// <returns></returns>
        public double this[int n, int m]
        {
            get 
            { 
                return _components[n, m]; 
            }
        }

        /// <summary>
        /// Возвращает детерминанту матрицы
        /// </summary>
        /// <returns></returns>
        public double GetDeterminant()
        {
            return GetLowerUpperPermutationDecomposition().GetDeterminant();
        }

        public static bool operator ==(Matrix B, Matrix A)
        {
            return B.Equals(A);
        }

        public static bool operator !=(Matrix B, Matrix A)
        {
            return !B.Equals(A);
        }

        public override bool Equals(object obj)
        {
            Matrix m = obj as Matrix;
            return m != null ? this.Equals(m) : false;
        }

        public override int GetHashCode()
        {
            return this._components.GetHashCode();
        }
        
        public bool Equals(Matrix secondMatrix)
        {
            int n = this.RowsCount;
            if (secondMatrix.RowsCount != n)
                return false;
            int m = this.ColumnsCount;
            if (secondMatrix.ColumnsCount != m)
                return false;
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < n; j++)
                {
                    if (secondMatrix._components[i, j] != _components[i, j])
                        return false;
                }
            }
            return true;
        }

        /// <summary>
        /// Возвращает инверсированную матрицу
        /// </summary>
        /// <returns></returns>
        public virtual Matrix GetInversedMatrix()
        {
            try
            {
                return new Matrix(GetLowerUpperPermutationDecomposition().GetInversedMatrixComponents());
            }
            catch (BIMatrixIllegalDimensionException)
            {
                return new Matrix(
                     GetResultOfSelfTransposedMultiplication().GetInversedMatrix().GetResultOfComponentsTransposedMultiplication(this));
            }            
        }

        /// <summary>
        /// Возвращает флаг квадратной матрицы
        /// </summary>
        public bool IsSquare
        {
            get 
            { 
                return this.RowsCount == this.ColumnsCount; 
            }
        }

        /// <summary>
        /// Возвращает декомпозицию нижней-верхней перестановки
        /// </summary>
        /// <returns></returns>
        protected LowerUpperPermutationDecomposition GetLowerUpperPermutationDecomposition()
        {
            if (_lowerUpperPermutationDecomposition == null)
                _lowerUpperPermutationDecomposition = new LowerUpperPermutationDecomposition(this);
            return _lowerUpperPermutationDecomposition;
        }

        /// <summary>
        /// Возвращает матрицу, полученную умножением заданной матрицы на коэффициент
        /// </summary>
        /// <param name="B"></param>
        /// <param name="C"></param>
        /// <returns></returns>
        public static Matrix operator*(Matrix B, double C)
        {
            return new Matrix(B.GetResultOfComponentsMultiplication(C));
        }

        /// <summary>
        /// Возвращает вектор, полученный умножением матрицы на вектор
        /// </summary>
        /// <param name="B"></param>
        /// <param name="v"></param>
        /// <returns></returns>
        public static Vector operator*(Matrix B, Vector v)
        {
            int n = B.RowsCount;
            int m = B.ColumnsCount;
            if (v.Dimensionality != m)
                throw new BIMatrixCantMultiplyVectorException(m, v.Dimensionality, null);
            return B.GetResultOfMultiplication(v);
        }

        /// <summary>
        /// Возвращает результат перемножения двух матриц
        /// </summary>
        /// <param name="B"></param>
        /// <param name="A"></param>
        /// <returns></returns>
        public static Matrix operator*(Matrix B, Matrix A)
        {
            if (A.RowsCount != B.ColumnsCount)
                throw new BIMatrixCantMultiplyMatrixException(B.ColumnsCount, A.RowsCount, null);
            return new Matrix(B.GetResultOfComponentsMultiplication(A));
        }

        /// <summary>
        /// Возвращает результат умножения на указанный коэффициент
        /// </summary>
        /// <param name="C"></param>
        /// <returns></returns>
        protected double[,] GetResultOfComponentsMultiplication(double C)
        {
            int n = this.RowsCount;
            int m = this.ColumnsCount;
            double[,] newComponents = new double[n, m];
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < m; j++)
                    newComponents[i, j] = C * _components[i, j];
            }
            return newComponents;
        }

        /// <summary>
        /// Возвращает результат умножения на указанную матрицу
        /// </summary>
        /// <param name="secondMatrix"></param>
        /// <returns></returns>
        protected double[,] GetResultOfComponentsMultiplication(Matrix secondMatrix)
        {
            int p = this.ColumnsCount;
            int n = this.RowsCount;
            int m = secondMatrix.ColumnsCount;
            double[,] newComponents = new double[n, m];
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < m; j++)
                {
                    double sum = 0;
                    for (int k = 0; k < p; k++)
                        sum += _components[i, k] * secondMatrix._components[k, j];
                    newComponents[i, j] = sum;
                }
            }
            return newComponents;
        }

        /// <summary>
        /// Возвращает результат умножения на матрицу, транспонированную относительно указанной матрицы
        /// </summary>
        /// <param name="secondOriginalMatrix"></param>
        /// <returns></returns>
        public Matrix GetResultOfTransposedMultiplication(Matrix secondOriginalMatrix)
        {
            if (secondOriginalMatrix.ColumnsCount != this.ColumnsCount)
                throw new BIMatrixCantMultiplyTransposedMatrixException(this.ColumnsCount, secondOriginalMatrix.ColumnsCount, null);
            return new Matrix(GetResultOfComponentsTransposedMultiplication(secondOriginalMatrix));
        }

        /// <summary>
        /// Возвращает результат умножения на матрицу, транспонированную относительно указанной матрицы
        /// </summary>
        /// <param name="secondOriginalMatrix"></param>
        /// <returns></returns>
        internal protected double[,] GetResultOfComponentsTransposedMultiplication(Matrix secondOriginalMatrix)
        {
            int p = this.ColumnsCount;
            int n = this.RowsCount;
            int m = secondOriginalMatrix.RowsCount;
            double[,] newComponents = new double[n, m];
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < m; j++)
                {
                    double sum = 0;
                    for (int k = 0; k < p; k++)
                        sum += _components[i, k] * secondOriginalMatrix._components[j, k];
                    newComponents[i, j] = sum;
                }
            }
            return newComponents;
        }

        /// <summary>
        /// Возвращает число строк в матрице
        /// </summary>
        public int RowsCount
        {
            get 
            { 
                return _components.GetLength(0); 
            }
        }

        /// <summary>
        /// Возвращает вектор, полученный умножением на вектор
        /// </summary>
        /// <param name="v"></param>
        /// <returns></returns>
        internal protected Vector GetResultOfMultiplication(Vector v)
        {
            int n = this.RowsCount;
            int m = this.ColumnsCount;
            double[] vectorComponents = new double[n];
            for (int i = 0; i < n; i++)
            {
                vectorComponents[i] = 0;
                for (int j = 0; j < m; j++)
                    vectorComponents[i] += _components[i, j] * v.Components[j];
            }
            return new Vector(vectorComponents);
        }

        /// <summary>
        /// Возвращает матрицу, полученную умножением на указанную матрицу
        /// </summary>
        /// <param name="secondMatrix"></param>
        /// <returns></returns>
        internal protected Matrix GetResultOfMultiplication(Matrix secondMatrix)
        {
            return new Matrix(GetResultOfComponentsMultiplication(secondMatrix));
        }

        /// <summary>
        /// Возвращает матрицу, полученную вычитанием указанной матрицы
        /// </summary>
        /// <param name="secondMatrix"></param>
        /// <returns></returns>
        internal protected Matrix GetResultOfSubtraction(Matrix secondMatrix)
        {
            return new Matrix(GetResultOfComponentsSubtraction(secondMatrix));
        }

        /// <summary>
        /// Возвращает разность двух матриц
        /// </summary>
        /// <param name="secondMatrix"></param>
        /// <returns></returns>
        public static Matrix operator-(Matrix b, Matrix a)
        {
            if (a.RowsCount != b.RowsCount || a.ColumnsCount != b.ColumnsCount)
                throw new BIMatrixCantAddOrSubtractException(a.RowsCount, a.ColumnsCount, b.RowsCount, b.ColumnsCount, null);
            return new Matrix(b.GetResultOfComponentsSubtraction(a));
        }

        /// <summary>
        /// Возвращает результат вычитания указанной матрицы
        /// </summary>
        /// <param name="secondMatrix"></param>
        /// <returns></returns>
        protected double[,] GetResultOfComponentsSubtraction(Matrix secondMatrix)
        {
            int n = this.RowsCount;
            int m = this.ColumnsCount;
            double[,] newComponents = new double[n, m];
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < n; j++)
                    newComponents[i, j] = _components[i, j] - secondMatrix._components[i, j];
            }
            return newComponents;
        }

        /// <summary>
        /// Возвращает клонированные элементы матрицы
        /// </summary>
        /// <returns></returns>
        public double[,] CloneComponents()
        {
            double[,] result = new double[this.RowsCount, this.ColumnsCount];
            for (int i = 0; i <= _components.GetUpperBound(0); i++)
            {
                for (int j = 0; j <= _components.GetUpperBound(1); j++)
                {
                    result[i, j] = _components[i, j];
                }
            }            
            return result;
        }

        /// <summary>
        /// Возвращает матрицу как строку
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            char[] separator = { '[', ' ' };
            int n = this.RowsCount;
            int m = this.ColumnsCount;
            for (int i = 0; i < n; i++)
            {
                separator[0] = '{';
                for (int j = 0; j < m; j++)
                {
                    sb.Append(separator);
                    sb.Append(_components[i, j]);
                    if (j == 0) 
                        separator[0] = ' ';
                }
                sb.Append('}');
                sb.Append('\n');
            }
            return sb.ToString();
        }

        /// <summary>
        /// Возвращает транспонированную матрицу
        /// </summary>
        /// <returns></returns>
        public virtual Matrix GetTransposedMatrix()
        {
            int n = this.RowsCount;
            int m = this.ColumnsCount;
            double[,] newComponents = new double[m, n];
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < m; j++)
                    newComponents[j, i] = _components[i, j];
            }
            return new Matrix(newComponents);
        }

        /// <summary>
        /// Возвращает матрицу, полученную умножением на свою собственную транспонированную матрицу
        /// </summary>
        /// <returns></returns>
        public SymmetricMatrix GetResultOfSelfTransposedMultiplication()
        {
            return new SymmetricMatrix(GetResultOfComponentsSelfTransposedMultiplication(this));
        }

        /// <summary>
        /// Возвращает матрицу, полученную умножением своей транспонированной матрицы на указанную матрицу
        /// </summary>
        /// <param name="secondMatrix"></param>
        /// <returns></returns>
        public Matrix GetResultOfSelfTransposedMultiplication(Matrix secondMatrix)
        {
            if (secondMatrix.RowsCount != this.RowsCount)
                throw new BIMatrixCantMultiplySelfTransposedMatrixException(this.RowsCount,secondMatrix.RowsCount, null);
            return new Matrix(GetResultOfComponentsSelfTransposedMultiplication(secondMatrix));
        }

        /// <summary>
        /// Возвращает результат умножения своей транспонированной матрицы на указанную матрицу
        /// </summary>
        /// <param name="secondMatrix"></param>
        /// <returns></returns>
        internal protected double[,] GetResultOfComponentsSelfTransposedMultiplication(Matrix secondMatrix)
        {
            int p = this.RowsCount;
            int n = this.ColumnsCount;
            int m = secondMatrix.ColumnsCount;
            double[,] newComponents = new double[n, m];
            for (int i = 0; i < n; i++)
            {
                for (int j = 0; j < m; j++)
                {
                    double sum = 0;
                    for (int k = 0; k < p; k++)
                        sum += _components[k, i] * secondMatrix._components[k, j];
                    newComponents[i, j] = sum;
                }
            }
            return newComponents;
        }
    }
}
